package co.yixiang.auth.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import co.yixiang.common.core.annotation.AnonymousAccess;
import co.yixiang.common.core.api.ApiResult;
import co.yixiang.common.core.api.YshopException;
import co.yixiang.common.core.constant.SecurityConstants;
import co.yixiang.common.core.constant.ShopConstants;
import co.yixiang.common.core.enums.ShopCommonEnum;
import co.yixiang.common.core.enums.SmsTypeEnum;
import co.yixiang.common.core.utils.ServletUtils;
import co.yixiang.common.redis.utils.RedisUtils;
import co.yixiang.common.security.service.RedisClientDetailsService;
import co.yixiang.mall.common.feign.RemoteMemberUserService;
import co.yixiang.mall.common.mall.domain.YxUser;
import co.yixiang.mall.common.mall.param.HLoginParam;
import co.yixiang.weixin.common.weixin.vo.LoginParam;
import co.yixiang.mall.common.mall.param.RegParam;
import co.yixiang.mall.common.mall.param.VerityParam;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 身份信息获取
 *
 * @author yshop
 */
@RestController
@RequestMapping("/oauth")
public class AppAuthController {
    private static final String USERNAME = "username";
    private static final String PASSWORD = "password";

    @Autowired
    private RemoteMemberUserService remoteMemberUserService;
    @Autowired
    private RedisClientDetailsService redisClientDetailsService;
    private final AuthorizationServerTokenServices tokenServices;
    private final AuthenticationManager authenticationManager;
    private final OAuth2RequestFactory oAuth2RequestFactory;
    private final RedisUtils redisUtil;
    public AppAuthController(AuthorizationServerTokenServices tokenServices, AuthenticationManager authenticationManager, OAuth2RequestFactory oAuth2RequestFactory,RedisUtils redisUtil) {
        this.tokenServices = tokenServices;
        this.authenticationManager = authenticationManager;
        this.oAuth2RequestFactory = oAuth2RequestFactory;
        this.redisUtil=redisUtil;
    }

    /**
     * H5登陆
     *
     * @param loginParam
     * @return
     */
    @AnonymousAccess
    @PostMapping(value = "/login")
    public ApiResult<Map<String, Object>> login(@Validated @RequestBody HLoginParam loginParam) {


        //去请求原有的逻辑获取用户信息
        ApiResult<YxUser> yxUserApiResult = remoteMemberUserService.HLogin(loginParam);

        if (yxUserApiResult.getStatus() != 200) {
            throw new YshopException(yxUserApiResult.getMsg());
        }

        //然后根据移动端用户信息账号密码去手动发起oauth2
        return getMapApiResult(yxUserApiResult,loginParam.getPassword());

    }

    /**
     * 小程序授权登录
     *
     * @param loginParam
     * @return
     */
    @AnonymousAccess
    @PostMapping(value = "/wxappLogin")
    public ApiResult<Map<String, Object>> wxapp(@Validated @RequestBody LoginParam loginParam, HttpServletRequest request) {


        //去请求原有的逻辑获取用户信息
        ApiResult<YxUser> yxUserApiResult = remoteMemberUserService.wxappLogin(loginParam);

        //然后根据移动端用户信息账号密码去手动发起oauth2
        return getMapApiResult(yxUserApiResult,ShopConstants.YSHOP_DEFAULT_PWD);

    }
    @AnonymousAccess
    @PostMapping("/register")
    public ApiResult<String> register(@Validated @RequestBody RegParam param) {
        Object codeObj = redisUtil.get("code_" + param.getAccount());
        if(codeObj == null){
            return ApiResult.fail("请先获取验证码");
        }
        String code = codeObj.toString();
        if (!StrUtil.equals(code, param.getCaptcha())) {
            return ApiResult.fail("验证码错误");
        }
        return remoteMemberUserService.register(param);
    }

    @AnonymousAccess
    @PostMapping("/register/verify")
    public ApiResult<String> verify(@Validated @RequestBody VerityParam param) {
        return remoteMemberUserService.verify(param);
    }


    /**
     * 方法提取
     *
     * @param yxUserApiResult
     * @return
     */
//    @LcnTransaction
    @GlobalTransactional
    private ApiResult<Map<String, Object>> getMapApiResult(ApiResult<YxUser> yxUserApiResult,String password) {
        if (yxUserApiResult.getStatus() != 200) {
            throw new YshopException(yxUserApiResult.getMsg());
        }
        OAuth2AccessToken auth2AccessToken = this.getOauth2AccessToken(yxUserApiResult.getData().getUsername(), password);
        Map<String, Object> map = new LinkedHashMap<>(2);
        //保存在线用户信息
        remoteMemberUserService.saveLoginOnUser(yxUserApiResult.getData(), auth2AccessToken.getValue());
        map.put("token", auth2AccessToken.getValue());
        map.put("expires_time", DateUtil.formatDateTime(auth2AccessToken.getExpiration()));
        return ApiResult.ok(map).setMsg("登陆成功");
    }


    /**
     * 公众号授权登录
     *
     * @param code
     * @param spread
     * @return
     */
    @AnonymousAccess
    @GetMapping(value = "/wechatLogin")
    public ApiResult<Map<String, Object>> wechatLogin(@RequestParam(value = "code") String code,
                                                      @RequestParam(value = "spread") String spread,
                                                      HttpServletRequest request) {

        //去请求原有的逻辑获取用户信息
        ApiResult<YxUser> yxUserApiResult = remoteMemberUserService.authLogin(code, spread);

        //然后根据移动端用户信息账号密码去手动发起oauth2
        return getMapApiResult(yxUserApiResult,ShopConstants.YSHOP_DEFAULT_PWD);

    }

    /**
     * 手动创建token
     *
     * @param username
     * @param password
     * @return
     */
    private OAuth2AccessToken getOauth2AccessToken(String username, String password) {
        final HttpServletRequest httpServletRequest = ServletUtils.getRequest();
        httpServletRequest.setAttribute(SecurityConstants.LOGIN_TYPE, SecurityConstants.APP_LOGIN);
        String loginClientId = "yshop";
        ClientDetails clientDetails = null;
        try {
            clientDetails = redisClientDetailsService.loadClientByClientId(loginClientId);
        } catch (Exception e) {
            throw new YshopException("获取移动端登录可用的Client失败");
        }
        if (clientDetails == null) {
            throw new YshopException("未找到移动端登录可用的Client");
        }
        Map<String, String> requestParameters = new HashMap<>(5);
        requestParameters.put(SecurityConstants.GRANT_TYPE, SecurityConstants.PASSWORD);
        requestParameters.put(USERNAME, username);
        requestParameters.put(PASSWORD, password);

        String grantTypes = String.join(",", clientDetails.getAuthorizedGrantTypes());


        Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
        ((AbstractAuthenticationToken) userAuth).setDetails(requestParameters);
        try {
            userAuth = authenticationManager.authenticate(userAuth);
        } catch (AccountStatusException ase) {
            //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
            throw new InvalidGrantException(ase.getMessage());
        } catch (BadCredentialsException e) {
            // If the username/password are wrong the spec says we should send 400/invalid grant
            throw new InvalidGrantException(e.getMessage());
        }
        if (userAuth == null || !userAuth.isAuthenticated()) {
            throw new InvalidGrantException("Could not authenticate user: " + username);
        }
        TokenRequest tokenRequest = new TokenRequest(requestParameters, clientDetails.getClientId(),
                clientDetails.getScope(),
                grantTypes);
        OAuth2Request storedOAuth2Request = oAuth2RequestFactory.createOAuth2Request(clientDetails, tokenRequest);
        return tokenServices.createAccessToken(new OAuth2Authentication(storedOAuth2Request, userAuth));
    }

}
